package backend

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
)

var (
	// ErrNotInitialized is returned when the backend is not initialized
	ErrNotInitialized = errors.New("backend is not initialized")

	// ErrInvalidRedisURL is returned when the Redis URL is invalid
	ErrInvalidRedisURL = errors.New("invalid Redis URL format, expected: redis://[username:password@]host:port/db")

	// ErrResultNotFound is returned when a result does not exist
	ErrResultNotFound = errors.New("result not found")
)

// RedisBackend implements the Backend interface using Redis
type RedisBackend struct {
	client *redis.Client
	ctx    context.Context
	expiry time.Duration // Default expiry for results
}

// NewRedisBackend creates a new Redis backend
func NewRedisBackend() *RedisBackend {
	return &RedisBackend{
		ctx:    context.Background(),
		expiry: 24 * time.Hour, // Default expiry of 24 hours
	}
}

// Initialize initializes the Redis backend with the given URL
// URL format: redis://[username:password@]host:port/db
func (rb *RedisBackend) Initialize(url string) error {
	// Parse Redis URL
	if !strings.HasPrefix(url, "redis://") {
		return ErrInvalidRedisURL
	}

	// Remove redis:// prefix
	url = strings.TrimPrefix(url, "redis://")

	// Extract auth info if present
	var auth string
	var hostPort string

	// Check if auth info is present (contains @)
	if idx := strings.LastIndex(url, "@"); idx != -1 {
		auth = url[:idx]
		hostPort = url[idx+1:]
	} else {
		hostPort = url
	}

	// Extract username and password from auth if present
	var username, password string
	if auth != "" {
		if idx := strings.Index(auth, ":"); idx != -1 {
			username = auth[:idx]
			password = auth[idx+1:]
		} else {
			// If no colon, treat the whole auth as the password
			password = auth
		}
	}

	// Parse host:port/db
	var db int
	if idx := strings.Index(hostPort, "/"); idx != -1 {
		// Try to parse db as int
		if hostPort[idx+1:] != "" {
			_, err := fmt.Sscanf(hostPort[idx+1:], "%d", &db)
			if err != nil {
				return err
			}
		}
		hostPort = hostPort[:idx]
	}

	// Create Redis client
	rb.client = redis.NewClient(&redis.Options{
		Addr:     hostPort,
		Username: username,
		Password: password,
		DB:       db,
	})

	// Test connection
	_, err := rb.client.Ping(rb.ctx).Result()
	return err
}

// Close closes the Redis backend connection
func (rb *RedisBackend) Close() error {
	if rb.client == nil {
		return ErrNotInitialized
	}

	return rb.client.Close()
}

// SetResult stores a task result
func (rb *RedisBackend) SetResult(taskID string, result []byte) error {
	if rb.client == nil {
		return ErrNotInitialized
	}

	key := fmt.Sprintf("celery-task-meta-%s", taskID)
	return rb.client.Set(rb.ctx, key, result, rb.expiry).Err()
}

// GetResult retrieves a task result
func (rb *RedisBackend) GetResult(taskID string) ([]byte, error) {
	if rb.client == nil {
		return nil, ErrNotInitialized
	}

	key := fmt.Sprintf("celery-task-meta-%s", taskID)
	val, err := rb.client.Get(rb.ctx, key).Result()
	if err != nil {
		if err == redis.Nil {
			return nil, ErrResultNotFound
		}
		return nil, err
	}

	// 检查任务状态，但返回完整的结果数据
	var resultData map[string]interface{}
	if err := json.Unmarshal([]byte(val), &resultData); err != nil {
		return nil, fmt.Errorf("failed to parse result: %w", err)
	}

	// 检查任务状态
	status, _ := resultData["status"].(string)
	if status != "SUCCESS" {
		errorMsg, _ := resultData["error"].(string)
		if errorMsg != "" {
			return nil, fmt.Errorf("task failed: %s", errorMsg)
		}
		return nil, fmt.Errorf("task status: %s", status)
	}

	// 返回完整的结果数据，而不是只提取result字段
	return []byte(val), nil
}

// HasResult checks if a result exists
func (rb *RedisBackend) HasResult(taskID string) (bool, error) {
	if rb.client == nil {
		return false, ErrNotInitialized
	}

	key := fmt.Sprintf("celery-task-meta-%s", taskID)
	exists, err := rb.client.Exists(rb.ctx, key).Result()
	if err != nil {
		return false, err
	}

	return exists > 0, nil
}

// DeleteResult removes a result
func (rb *RedisBackend) DeleteResult(taskID string) error {
	if rb.client == nil {
		return ErrNotInitialized
	}

	key := fmt.Sprintf("celery-task-meta-%s", taskID)
	return rb.client.Del(rb.ctx, key).Err()
}

// SetExpiry sets the expiry time for results
func (rb *RedisBackend) SetExpiry(expiry time.Duration) {
	rb.expiry = expiry
}
